
from pyevolve import G1DList
from pyevolve import GSimpleGA

from datetime import date

from interface.CommandLine import CommandLine
 
from MonthGenerator import MonthGenerator
from TwoWeekServiceCycleEvaluator import TwoWeekServiceCycleEvaluator
from SingleShiftPerDayEvaluator import SingleShiftPerDayEvaluator
from VacationDaysEvaluator import VacationDaysEvaluator
from CallCycleAndNumberCallsEvaluator import CallCycleAndNumberCallsEvaluator
from ClinicEvaluator import ClinicEvaluator
from WeekendCallEvaluator import WeekendCallEvaluator
from EvaluatorHelper import EvaluatorHelper
from AggregateCall import AggregateCall
from DoctorDaysOff import DoctorDaysOff 
from Doctors import Doctors
from ServiceType import ServiceType
from GAParameters import GAParameters

class CallScheduleGenerator():

    def __init__(self, logger):
        
        self._logger = logger
        
        ''' want empty map variable for storing counts '''
        self._initCount = dict()
        for i in range(0, len (Doctors().doctorNames)):
            self._initCount[i] = 0;
        
        ''' number of calls completed in current year by doctors '''
        self._callTally = [
                            [ [2, 0, 3, 1], [2, 0, 1, 0], [4, 1, 1, 0], [2, 1, 2, 0], [3, 1, 2, 0] ],
                            [ [4, 0, 2, 0], [2, 1, 1, 0], [4, 1, 1, 0], [2, 0, 2, 0], [3, 1, 1, 0] ],
                            [ [3, 0, 2, 0], [3, 0, 2, 0], [3, 1, 1, 0], [3, 0, 1, 0], [3, 1, 2, 0] ],
                            [ [3, 1, 0, 0], [2, 2, 2, 1], [3, 0, 2, 0], [4, 1, 1, 0], [2, 0, 2, 1] ],
                            [ [3, 0, 2, 0], [2, 0, 2, 0], [3, 0, 1, 0], [2, 2, 2, 0], [4, 0, 2, 0] ],
                            [ [2, 2, 2, 1], [4, 1, 1, 0], [2, 1, 2, 0], [3, 0, 2, 1], [2, 1, 1, 0] ],
                        ]
        
        '''
        may
        '''
        
        month = 6
        
        self._daysOff = DoctorDaysOff().getDaysOffByMonth(month)
        self._noCall = DoctorDaysOff().getNoCallByMonth(month)
        
        calls = {
                 'nicu': [ Doctors().doctorIds['GUDAVALLI'], Doctors().doctorIds['NADROO'], Doctors().doctorIds['NADROO'], Doctors().doctorIds['GAD'], Doctors().doctorIds['GAD']], 
                 'sd': [Doctors().doctorIds['SUK'], Doctors().doctorIds['RON'], Doctors().doctorIds['RON'], Doctors().doctorIds['SUK'], Doctors().doctorIds['SUK']]
                 }        

        '''
        june
        
        calls = {'nicu': [self._doctorIds['GAD'], self._doctorIds['RON'], self._doctorIds['RON'], self._doctorIds['SUK'], self._doctorIds['SUK']], 
                 'sd': [self._doctorIds['SUK'], self._doctorIds['NADROO'], self._doctorIds['NADROO'], self._doctorIds['GUDAVALLI'], self._doctorIds['GUDAVALLI']]}
    
        '''
        
        [self._schedule, 
         self._weekBoundary,
         self._callIndex, 
         self._nicuIndex, 
         self._stepDownIndex, 
         self._clinicIndex,
         self._weekendCallIndex,
         self._weekdayCallDays,
         self._fridayCallDays,
         self._weekendCallDays,
         self._holidayCallDays
         ] = MonthGenerator().generate(month, 2011)
        
        self._shiftCycle = self._schedule[:7]
        
        multiplier = 2
        
        self._eval = [
                      TwoWeekServiceCycleEvaluator(self._nicuIndex, self._stepDownIndex, self._weekBoundary, calls, self._initCount, multiplier),
                      SingleShiftPerDayEvaluator(self._schedule, self._initCount, multiplier),
                      VacationDaysEvaluator(self._schedule, self._daysOff, self._noCall, multiplier),
                      CallCycleAndNumberCallsEvaluator(self._callIndex, self._callTally, self._initCount),
                      ClinicEvaluator(self._callIndex, self._clinicIndex, self._initCount),
                      WeekendCallEvaluator(self._weekendCallIndex, self._initCount)
                      ]
        
        return       
    
    def sumCalls(self, individual, call):
        count = self._initCount.copy()
        
        for day in call:
            [index, _] = EvaluatorHelper.getCallShiftIndex(day, self._shiftCycle)
            count[individual[index]] += 1
            
        return count

    
    def ConvergenceCriteria(self, ga_engine):
        individual = ga_engine.bestIndividual()
        return reduce(lambda x, y: x and y, [f.evaluateConvergenceCriteria(individual, self._attemptNumber) for f in self._eval])
    
    def run(self):
        gaParameters = GAParameters(self._schedule)
        
        genome = G1DList.G1DList(gaParameters.numChromosomes)
        genome.setParams(rangemin = 0, rangemax = len (Doctors().doctorNames) - 1)
        
        for f in self._eval:
            genome.evaluator.add(f.evaluate)
                
        attempts = 1
        self._attemptNumber = 0
        
        while self._attemptNumber < attempts:
        
            self._logger.info("Attempt number: %d" % (self._attemptNumber + 1))
            
            ga = GSimpleGA.GSimpleGA(genome)
            ga.setPopulationSize(gaParameters.populationSize)
            ga.setGenerations(gaParameters.numberGenerations)
            ga.setMinimax(GSimpleGA.Consts.minimaxType["maximize"])
            ga.terminationCriteria.set(self.ConvergenceCriteria)
            #ga.setElitism(False)
            ga.evolve(freq_stats = gaParameters.numberGenerations / 10)
            
            bestIndividual = ga.bestIndividual()
            
            successful = self.ConvergenceCriteria(ga) 
            #reduce(lambda x, y: x and y, [f.evaluateConvergenceCriteria(bestIndividual, attemptNumber) for f in self._eval])
            
            if successful: 
                self._logger.info("SUCCESSFUL")
                break
            else: 
                self._logger.info("FAILED")
                
            self._attemptNumber += 1

        print bestIndividual
        CommandLine().printIndividual(bestIndividual, self._schedule)
        
        for f in self._eval:
            f.evaluate(bestIndividual, True)
        
        weekdayCallDaysCounts = self.sumCalls(bestIndividual, self._weekdayCallDays)
        print "Weekday calls: %s" % CommandLine().printDoctorCounts(weekdayCallDaysCounts)
        
        fridayCallDaysCounts = self.sumCalls(bestIndividual, self._fridayCallDays)
        #print "Friday calls: %s" % self.printDoctorCounts(fridayCallDaysCounts)
        
        weekendCallDaysCounts = self.sumCalls(bestIndividual, self._weekendCallDays)
        #print "Weekend calls: %s" % self.printDoctorCounts(weekendCallDaysCounts)
        
        holidayCallDaysCounts = self.sumCalls(bestIndividual, self._holidayCallDays)
        #print "Holiday calls: %s" % self.printDoctorCounts(holidayCallDaysCounts)
        
        callCounts = dict()
        
        for doctor in range(len(Doctors().doctorNames)):
            callCounts[doctor] = [ weekdayCallDaysCounts[doctor], fridayCallDaysCounts[doctor], weekendCallDaysCounts[doctor], holidayCallDaysCounts[doctor] ]
            
        CommandLine().printCall(self._callTally)
        
        for doctorId in range(len(self._callTally)):
            self._callTally[doctorId].append( callCounts[doctorId] )

        CommandLine().printCall(self._callTally)
        
        print "Doctor\t\tW/day\tFri\tW/end\tTotal\tHol"
        for doctor in range(len(Doctors().doctorNames)):
            print "%s%s%d\t%d\t%d\t%d\t%d" % (Doctors().doctorNames[doctor], 
                                          "\t" * (2 - len(Doctors().doctorNames[doctor]) // 8), 
                                          weekdayCallDaysCounts[doctor], 
                                          fridayCallDaysCounts[doctor], 
                                          weekendCallDaysCounts[doctor],
                                          weekdayCallDaysCounts[doctor] + fridayCallDaysCounts[doctor] + weekendCallDaysCounts[doctor],
                                          holidayCallDaysCounts[doctor])
        
def main():
    
    s = CallScheduleGenerator()
    s.run()

if __name__ == "__main__":
    main()        
